//*********************************************************************
// Copyright (C) 2013 Hell Prototypes / www.hellprototypes.com
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
//
//********************************************************************

#include "USB/usb.h"
#include "USB/usb_function_generic.h"
#include "HardwareProfile.h"
#include "GenericTypeDefs.h"
#include "version.h"
#include "user.h"
#include "usbcmd.h"
#include "eeprom_flags.h"
#include "utils.h"
#include "blinker.h"
#include "spi.h"

// USB data packet definitions
typedef union DATA_PACKET
{
    BYTE _byte[USBGEN_EP_SIZE];     //For byte access
    WORD _word[USBGEN_EP_SIZE / 2];   //For word access(USBGEN_EP_SIZE must be even)
    struct // for version - compatible to bootloader version return format
    {
        USBCMD cmd;
        BYTE   UNUSE;
        BYTE   ver_Byte0;
        BYTE   ver_Byte1;
    };
    struct // for SPI
    {
        USBCMD cmd;
        BYTE   len;
        BYTE   SPI[USBGEN_EP_SIZE - 2];
    };
} DATA_PACKET;

#define MIPS 12                         // Number of processor instructions per microsecond.
#define MAX_BYTE_VAL 0xFF               // Maximum value that can be stored in a byte.
#define NUM_ACTIVITY_BLINKS 8          // Indicate activity by blinking the LED this many times.
#define BLINK_SCALER 10                 // Make larger to stretch the time between LED blinks.

#pragma udata access my_access
static near DWORD lcntr;                    // Large counter for fast loops.
static near BYTE last_key;
static near BYTE fpga_app;

#pragma udata
static USB_HANDLE OutHandle[2] = {0,0}; // Handles to endpoint buffers that are receiving packets from the host.
static BYTE OutIndex           = 0;     // Index of endpoint buffer has received a complete packet from the host.
static DATA_PACKET *OutPacket;          // Pointer to the buffer with the most-recently received packet.
static BYTE OutPacketLength    = 0;     // Length (in bytes) of most-recently received packet.
static USB_HANDLE InHandle[2]  = {0,0}; // Handles to ping-pong endpoint buffers that are sending packets to the host.
static BYTE InIndex            = 0;     // Index of the endpoint buffer that is currently being filled before being sent to the host.
static DATA_PACKET *InPacket;           // Pointer to the buffer that is currently being filled.
                 // Timer for RUNTEST command.

#pragma udata usbram2
static DATA_PACKET InBuffer[2];     // Ping-pong buffers in USB RAM for sending packets to host.
static DATA_PACKET OutBuffer[2];    // Ping-pong buffers in USB RAM for receiving packets from host.


#pragma code

BYTE ReadEeprom(BYTE address)
{
    EECON1 = 0x00;
    EEADR = address;
    EECON1bits.RD = 1;
    return EEDATA;
}

void WriteEeprom(BYTE address, BYTE data)
{
    EEADR = address;
    EEDATA = data;
    EECON1 = 0b00000100;    //Setup writes: EEPGD=0,WREN=1
    EECON2 = 0x55;
    EECON2 = 0xAA;
    EECON1bits.WR = 1;
    while(EECON1bits.WR);       //Wait till WR bit is clear
}
//mode:
//   1: FPGA IAP auto boot (auto boot go APP0)
//   0: FPGA IAP auto boot disable (stay in IAP mode)
BYTE Reset_FPGA(BYTE mode)
{
    DWORD config_delay;
    BYTE ret = 0;

    FPGACLK_OFF();
    FPGACLK = 0;
    if(mode) {
        mosi_ctrl(1);
    } else {
        mosi_ctrl(0);
    }

    // Try to configure the FPGA from the serial flash.
    PROGB = 0;                  // Erase the FPGA.
    insert_delay(1000);
    PROGB = 1;                  // Release FPGA and let it try to configure from the serial flash.
    // Now wait for a while and see if the FPGA configuration done pin goes high.
    for(config_delay=0L; config_delay<500000L; config_delay++)
    {
        if(DONE == 1) {
            ret = 1;
            break;
        }   
    }

    FPGACLK_ON();               // Give the FPGA a clock whether it is configured or not.

    return ret;
}
void delay(void)
{
    lcntr = 0xFFFF;
    for(lcntr = 0xFFFF; lcntr; lcntr--);
}
void UserInit( void )
{
    // Initialize the I/O pins.
    // Enable high slew-rate for the I/O pins.
    SLRCON = 0;

    // Initialize SPI Pins
    spi_init();

    // Initialize the FPGA configuration pins.
    INIT_DONE();
    INIT_PROGB();
    // Initialize the clock to the FPGA.
    INIT_FPGACLK();
    // Initialize the status LED.
    INIT_LED();
    INIT_KEY();

    InitBlinker();  // Initialize LED status blinker.

    // Initialize interrupts.
    RCONbits.IPEN     = 1;      // Enable prioritized interrupts.
    INTERRUPTS_ON();            // Enable high and low-priority interrupts.

    Reset_FPGA(1);
    last_key = 1;
    fpga_app = 0;
}

// This function is called when the device becomes initialized, which occurs after the host sends a
// SET_CONFIGURATION (wValue not = 0) request.  This callback function should initialize the endpoints
// for the device's usage according to the current configuration.
void USBCBInitEP( void )
{
    // Enable the endpoint.
    USBEnableEndpoint( USBGEN_EP_NUM, USB_OUT_ENABLED | USB_IN_ENABLED | USB_HANDSHAKE_ENABLED | USB_DISALLOW_SETUP );
    // Now begin waiting for the first packets to be received from the host via this endpoint.
    OutIndex = 0;
    OutHandle[0] = USBGenRead( USBGEN_EP_NUM, (BYTE *)&OutBuffer[0], USBGEN_EP_SIZE );
    OutHandle[1] = USBGenRead( USBGEN_EP_NUM, (BYTE *)&OutBuffer[1], USBGEN_EP_SIZE );
    // Initialize the pointer to the buffer which will return data to the host via this endpoint.
    InIndex = 0;
    InPacket  = &InBuffer[0];
}

void ProcessIO( void )
{
    if ( ( USBGetDeviceState() < CONFIGURED_STATE ) || USBIsDeviceSuspended() )
        return;

    ServiceRequests();
}

void ServiceRequests( void )
{
    BYTE num_return_bytes;          // Number of bytes to return in response to received command.
    BYTE cmd;                       // Store the command in the received packet.
    BYTE i;

    // Process packets received through the primary endpoint.
    if ( !USBHandleBusy( OutHandle[OutIndex] ) )
    {
        num_return_bytes = 0;   // Initially, assume nothing needs to be returned.

        // Got a packet, so start getting another packet while we process this one.
        OutPacket        = &OutBuffer[OutIndex]; // Store pointer to just-received packet.
        OutPacketLength  = USBHandleGetLength( OutHandle[OutIndex] );   // Store length of received packet.
        cmd              = OutPacket->cmd;

        blink_counter    = NUM_ACTIVITY_BLINKS; // Blink the LED whenever a USB transaction occurs.

        switch ( cmd )  // Process the contents of the packet based on the command byte.
        {
            case READ_VERSION_CMD:
                InPacket->cmd = cmd;
                //InPacket->UNUSE = 0;
                InPacket->ver_Byte0 = (MAJOR_VERSION << 4) | MINOR_VERSION;
                InPacket->ver_Byte1 = USER_APP_FLAG;
                num_return_bytes               = 4;
                break;

            case SPI_CTRL_CMD:
                //(BYTE data)
                InPacket->cmd = cmd;
                InPacket->len = OutPacket->len;
                for(i=0; i<OutPacket->len; i++) {
                    InPacket->SPI[i] = spi_play_one_byte(OutPacket->SPI[i]);
                }
                num_return_bytes = OutPacket->len + 2;
                break;

            case SPI_FLASH_WR:
                //Write enable
                spi_play_one_byte(0xC1);
                spi_play_one_byte(0x06);

                //Send data
                for(i=0; i<OutPacket->len; i++) {
                    spi_play_one_byte(OutPacket->SPI[i]);
                }

                //Wait
                cmd = 0;
                i = 50;
                while(i--) {
                    spi_play_one_byte(0xC2);
                    spi_play_one_byte(0x05);
                    if(!(spi_play_one_byte(0xff) & 1)) {
                        cmd = 1;
                        break;
                    }
                }
                InPacket->cmd = cmd;
                num_return_bytes = 1;
                break;

            case RESET_FPGA:
                InPacket->cmd = Reset_FPGA(0);;
                num_return_bytes = 1;
                break;

            case RESET_CMD:
                // When resetting, make sure to drop the device off the bus
                // for a period of time. Helps when the device is suspended.
                UCONbits.USBEN = 0;
                delay();
                Reset();
                break;

            default:
                num_return_bytes = 0;
                break;
        } /* switch */

        // This command packet has been handled, so get another.
        OutHandle[OutIndex] = USBGenRead( USBGEN_EP_NUM, (BYTE *)&OutBuffer[OutIndex], USBGEN_EP_SIZE );
        OutIndex ^= 1; // Point to next ping-pong buffer.

        // Packets of data are returned to the PC here.
        // The counter indicates the number of data bytes in the outgoing packet.
        if ( num_return_bytes != 0U )
        {
            InHandle[InIndex] = USBGenWrite( USBGEN_EP_NUM, (BYTE *)InPacket, num_return_bytes ); // Now send the packet.
            InIndex ^= 1;
            while ( USBHandleBusy( InHandle[InIndex] ) )
            {
                ;                           // Wait until transmitter is not busy.
            }
            InPacket = &InBuffer[InIndex];
        }
    } else {
        if((KEY == 0) && (last_key == 1)) {
            if(fpga_app == 0) {
                fpga_app = 1;
                Reset_FPGA(0);
                delay();
                //Set Multi-Boot Address : APP1
                spi_play_one_byte(0x41);
                spi_play_one_byte(0x03);
                spi_play_one_byte(0x04);
                spi_play_one_byte(0x50);
                spi_play_one_byte(0x00);
                delay(); 
                //Reconfig FPGA
                spi_play_one_byte(0x40);
            } else {
                fpga_app = 0;
                Reset_FPGA(1);
            }
        }
        last_key = KEY;
    }
} /* ServiceRequests */
